/**********************************************************************\

 Copyright 2012 Diggory Blake

 This file is part of klip-engine

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.

\**********************************************************************/

#include "kimagegl.h"
#include "kgraphicsgl.h"
#include "ksystem.h"
#include "kerror.h"
#include "kdata.h"
#include "kimagefile.h"
#include <string.h>

kImageGL::kImageGL(kGraphicsGL* graphics, GLuint frameBuffer, GLuint texture) : kImage(graphics->getEngine()) {
	_frameBuffer = frameBuffer;
	_texture = texture;
	_graphics = graphics;

	if (_texture) {
		uint32_t alphaType;

		GLint oldtexture = 0;
		glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldtexture);
		glBindTexture(GL_TEXTURE_2D, texture);

		glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, (GLint*)&_size.x);
		glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, (GLint*)&_size.y);
		glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_ALPHA_TYPE, (GLint*)&alphaType);

		glBindTexture(GL_TEXTURE_2D, oldtexture);

		_options |= keImage_Drawable;

		if (alphaType == GL_NONE)
			_options |= keImage_Opaque;

	} else {
		int oldDrawBuffer, oldReadBuffer;
		glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING_EXT, &oldDrawBuffer);
		glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING_EXT, &oldReadBuffer);

		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _frameBuffer);
		
		GLint objtype = 0, oldobjname = 0;
		GLint objname = 0;
		if (_frameBuffer) {
			glGetFramebufferAttachmentParameterivEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT, &objtype);
			glGetFramebufferAttachmentParameterivEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT, &objname);
		}

		uint32_t alphaSize = 0;
		switch (objtype) {
		case GL_RENDERBUFFER:
			glGetIntegerv(GL_RENDERBUFFER_BINDING_EXT, &oldobjname);
			glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, objname);
			glGetRenderbufferParameterivEXT(GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_WIDTH_EXT, (GLint*)&_size.x);
			glGetRenderbufferParameterivEXT(GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_HEIGHT_EXT, (GLint*)&_size.y);
			glGetRenderbufferParameterivEXT(GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_ALPHA_SIZE, (GLint*)&alphaSize);
			glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, oldobjname);

			if (!alphaSize)
				_options |= keImage_Opaque;

			break;
		default:
			{
				_size = _graphics->_window->getSize();
				_options |= keImage_Opaque;
			}
			break;
		}

		glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, oldDrawBuffer);
		glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, oldReadBuffer);
	}

	if (_frameBuffer || !_texture)
		_options |= keImage_RenderTarget;

	_options |= keImage_Lockable;

}
kImageGL::~kImageGL() {
	if (_frameBuffer) {
		glDeleteFramebuffersEXT(1, &_frameBuffer);
		_frameBuffer = 0;
	}
	if (_texture) {
		glDeleteTextures(1, &_texture);
		_texture = 0;
	}
}
void kImageGL::save(kStream* stream, keImageFormat format) const {
	_graphics->_window->activateGLContext(_graphics->_context);

	GLint oldtexture = 0;
	glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldtexture);

	glBindTexture(GL_TEXTURE_2D, _texture);

	kImageHeader header;
	header.format = format;
	header.width = _size.x;
	header.height = _size.y;
	header.depth = (getOptions() & keImage_Opaque) ? 3 : 4;
	GLenum fmt = (header.depth == 3) ? GL_BGR : GL_BGRA;

	uint8_t* dest = (uint8_t*)malloc(header.width*header.height*header.depth);
	glGetTexImage(GL_TEXTURE_2D, 0, fmt, GL_UNSIGNED_BYTE, dest);

	kImageFile::save(stream, &header, [&](uint32_t y, uint8_t* data) {
		memcpy(data, dest + y*header.width*header.depth, header.width*header.depth);
	});

	free(dest);

	glBindTexture(GL_TEXTURE_2D, oldtexture);
}
void kImageGL::doProcess(kRect* region, bool readonly, std::function<void (const kVec2i&, kColor&)> lambda) const {
	_graphics->_window->activateGLContext(_graphics->_context);

	GLint oldtexture = 0;
	glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldtexture);

	glBindTexture(GL_TEXTURE_2D, _texture);

	size_t depth = (getOptions() & keImage_Opaque) ? 3 : 4;
	GLenum fmt = (depth == 3) ? GL_BGR : GL_BGRA;

	kDataBuffer buffer(_size.x*_size.y*depth);

	glGetTexImage(GL_TEXTURE_2D, 0, fmt, GL_UNSIGNED_BYTE, buffer.getPtr());

	kRect rect;
	if (region) {
		rect = *region;
	} else {
		rect.left = 0;
		rect.top = 0;
		rect.width = _size.x;
		rect.height = _size.y;
	}

	const float scale = 1.0f/255.0f;
	const float invscale = 1.0f/scale;
	if (getOptions() & keImage_Opaque) {
		for (uint32_t y = rect.top; y < (uint32_t)(rect.top + rect.height); y++)
			for (uint32_t x = rect.left; x < (uint32_t)(rect.left + rect.width); x++) {
				uint8_t* ptr = buffer.getPtr() + (y*_size.x + x)*depth;

				kColor color(ptr[2]*scale, ptr[1]*scale, ptr[0]*scale);
				lambda(kVec2i(x, y), color);
				
				if (!readonly) {
					ptr[2] = (uint8_t)(color.r*invscale + 0.5f);
					ptr[1] = (uint8_t)(color.g*invscale + 0.5f);
					ptr[0] = (uint8_t)(color.b*invscale + 0.5f);
				}
			}
	} else {
		for (uint32_t y = rect.top; y < (uint32_t)(rect.top + rect.height); y++)
			for (uint32_t x = rect.left; x < (uint32_t)(rect.left + rect.width); x++) {
				uint8_t* ptr = buffer.getPtr() + (y*_size.x + x)*depth;

				kColor color(ptr[3]*scale, ptr[2]*scale, ptr[1]*scale, ptr[0]*scale);
				lambda(kVec2i(x, y), color);

				if (!readonly) {
					ptr[3] = (uint8_t)(color.a*invscale + 0.5f);
					ptr[2] = (uint8_t)(color.r*invscale + 0.5f);
					ptr[1] = (uint8_t)(color.g*invscale + 0.5f);
					ptr[0] = (uint8_t)(color.b*invscale + 0.5f);
				}
			}
	}

	if (!readonly) {
		GLenum ifmt = (depth == 3) ? GL_RGB8 : GL_RGBA8;

		glTexImage2D(GL_TEXTURE_2D, 0, ifmt, _size.x, _size.y, 0, fmt, GL_UNSIGNED_BYTE, buffer.getPtr());
	}

	glBindTexture(GL_TEXTURE_2D, oldtexture);
}
